package net.takela.common.security;

import net.takela.common.security.filter.AuthFilterSecurityInterceptor;
import net.takela.common.security.filter.LoginFilter;
import net.takela.common.security.filter.RequestParamSignAuthFilter;
import net.takela.common.security.filter.TokenAuthFilter;
import net.takela.common.security.service.*;
import net.takela.common.security.handler.*;
import net.takela.common.security.handler.*;
import net.takela.common.security.service.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhangyinghui
 * create at 2023/7/11
 */
@Configuration
public class SecurityAutoConfig {

    @Value("${auth.token.key}")
    private String authTokenKey;
    
    /**
     * 
     */
    @Bean
    @ConfigurationProperties(prefix = "auth.token")
    @ConditionalOnMissingBean(AuthTokenManager.class)
    @Order(1)
    public AuthTokenManager authTokenManager(){
        return new AuthTokenManager();
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(UserDetailsService.class)
    public UserDetailsService userDetailsService(){
        return new UserDetailServiceImpl();
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(UserAuthTokenService.class)
    public UserAuthTokenService userAuthTokenService(){
        return new UserAuthTokenServiceImpl();
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(AbstractRememberMeServices.class)
    public AbstractRememberMeServices rememberMeServices(UserDetailsService userDetailsService, UserAuthTokenService userAuthTokenService){
        return new TokenRememberServiceImpl(authTokenKey, userDetailsService, userAuthTokenService);
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(AuthenticationSuccessHandler.class)
    public AuthSuccessHandler authenticationSuccessHandler(){
        return new AuthSuccessHandler(authTokenManager(), userAuthTokenService());
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(AuthenticationFailureHandler.class)
    public AuthenticationFailureHandler authenticationFailureHandler(){
        return new AuthFailureHandler();
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(SysFunctionLoadService.class)
    public SysFunctionLoadService sysFunctionLoadService(){
        return new SysFunctionLoadService();
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(RequestParamSignAuthFilter.class)
    @ConfigurationProperties(prefix = "auth.param.sign")
    public RequestParamSignAuthFilter requestParamSignAuthFilter(){
        return new RequestParamSignAuthFilter();
    }
    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(TokenAuthFilter.class)
    public TokenAuthFilter tokenAuthFilter(AuthTokenManager authTokenManager, SecurityProperties securityProperties){
        return new TokenAuthFilter(authTokenManager, securityProperties);
    }
    @Bean
    @ConfigurationProperties(prefix = "auth.spring-security")
    /**
     * 
     */
    public SecurityProperties securityProperties(){
        return new SecurityProperties();
    }

    
    /**
     * 
     */
    @Bean
    @ConditionalOnMissingBean(AuthFilterSecurityInterceptor.class)
    public AuthFilterSecurityInterceptor authFilterSecurityInterceptor(AuthenticationConfiguration authenticationConfiguration, SysFunctionLoadService sysFunctionLoadService, SecurityProperties securityProperties) throws Exception {
        List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
        voters.add(new PermissionVoter());

        AuthFilterSecurityInterceptor filterSecurityInterceptor = new AuthFilterSecurityInterceptor(securityProperties);
        filterSecurityInterceptor.setSecurityMetadataSource(new AuthSecurityMetadataSource(sysFunctionLoadService.getRequestConfigMap()));
        filterSecurityInterceptor.setAccessDecisionManager(new AffirmativeBased(voters));
        filterSecurityInterceptor.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
        filterSecurityInterceptor.afterPropertiesSet();
        return filterSecurityInterceptor;
    }
    
    /**
     * 
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer(SecurityProperties securityProperties) {
        return (web) -> web.debug(true).ignoring().requestMatchers(securityProperties.getAnonymousUrls().toArray(new String[0]));
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    /**
     * 
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        PersistentTokenRepository tokenRepository = new TokenRepositoryImpl();
        return tokenRepository;
    }

    
    /**
     * 
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http,
                                                   SecurityProperties securityProperties,
                                                   TokenAuthFilter tokenAuthFilter,
                                                   RequestParamSignAuthFilter requestParamSignAuthFilter,
                                                   AuthFilterSecurityInterceptor authFilterSecurityInterceptor,
                                                   AuthenticationConfiguration authenticationConfiguration,
                                                   AuthTokenManager authTokenManager,
                                                   AuthenticationSuccessHandler authenticationSuccessHandler,
                                                   AuthenticationFailureHandler authenticationFailureHandler) throws Exception{
//        tokenAuthFilter.setUserClass(User.class);
        http.csrf( c -> c.disable())
                .sessionManagement( sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests( r -> r.requestMatchers(securityProperties.getPermitUrls().toArray(new String[0])).permitAll().anyRequest().authenticated())
                .exceptionHandling(e -> e.defaultAuthenticationEntryPointFor(new AuthEntryPoint(), AnyRequestMatcher.INSTANCE)
                        .accessDeniedHandler(new AuthDeniedHandler())
                        .authenticationEntryPoint(new AuthEntryPoint())
                )
                .addFilterBefore(tokenAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(requestParamSignAuthFilter, FilterSecurityInterceptor.class)
                .addFilterAfter(authFilterSecurityInterceptor, FilterSecurityInterceptor.class);
        if (securityProperties.getLoginEnabled()){
            http.addFilterAt(new LoginFilter(authenticationConfiguration.getAuthenticationManager(), authTokenManager, authenticationSuccessHandler, authenticationFailureHandler), UsernamePasswordAuthenticationFilter.class);
        }
        return http.build();
    }
}
